/*
 * Copyright (C) 2010 M. Hautle.
 * 
 * This file is part of TrackedBean.
 * 
 * TrackedBean is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * TrackedBean is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with TrackedBean. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.trackedbean.loaders;

import java.util.*;
import ch.trackedbean.internal.*;
import ch.trackedbean.loaders.internal.*;
import ch.trackedbean.loaders.lists.*;
import ch.trackedbean.loaders.objects.*;

/**
 * The {@link DataManager} is the central unit responsible for loading/creating/updating/removing objects using {@link IObjectHandler}s.<br>
 * It supports also list loading using {@link IListLoader}s.<br>
 * Callers caching/holding the data retrieved by this manager should register them self as {@link IDataListener} to avoid that they use stale data.<br>
 * You may implement your own {@link ILoaderStrategy} to adapt the behavior of the manager.
 * 
 * @author M. Hautle
 * @since 08.10.2010
 */
public class DataManager {
    /** The loaders. */
    private static final LoaderList<IListLoader> LIST_LOADERS = new LoaderList<IListLoader>(IListLoader.class);

    /** The loaders. */
    private static final LoaderList<IObjectHandler> OBJECT_HANDLERS = new LoaderList<IObjectHandler>(IObjectHandler.class);

    /** The load strategy. */
    private static final ILoaderStrategy STRATEGY;

    static {
        STRATEGY = ServiceHelper.getService(ILoaderStrategy.class, DefaultLoadStrategy.class);
    }

    // CONTINUE callback support

    /**
     * Hidden constructor.
     */
    private DataManager() {
    }

    /**
     * Tries to load the desired list from the cache (using only the type information).<br>
     * This is a convenience method for {@link #getList(Class, Object, Map)}.
     * 
     * @param <T> The content type of the list
     * @param type The content type of the list
     * @return A copy of the list
     * @throws ListLoadingException If something went wrong while loading the list
     * @throws NoLoaderException If no loader is registered for the given type
     */
    public static <T> List<T> getList(Class<T> type) throws ListLoadingException, NoLoaderException {
        return getList(type, null);
    }

    /**
     * Tries to load the desired list from the cache. If no such entry exists in the cache the corresponding {@link IListLoader} service will be looked up and
     * invoked. This is a convenience method for {@link #getList(Class, Object, Map)}.
     * 
     * @param <T> The content type of the list
     * @param <D> The discriminator type
     * @param type The content type of the list
     * @param subType Additional discriminator specifying the "subtype" to load (may be null)
     * @return A copy of the list
     * @throws ListLoadingException If something went wrong while loading the list
     * @throws NoLoaderException If no loader is registered for the given type
     */
    public static <T, D> List<T> getList(Class<T> type, D subType) throws ListLoadingException, NoLoaderException {
        return getList(type, subType, null);
    }

    /**
     * Tries to load the desired list from the cache. If no such entry exists in the cache the corresponding {@link IListLoader} service will be looked up and
     * invoked.
     * 
     * @param <T> The content type of the list
     * @param <D> The discriminator type
     * @param <K> The key type for the parameter map
     * @param <V> The value type for the parameter map
     * @param type The content type of the list
     * @param subType Additional discriminator specifying the "subtype" to load (may be null)
     * @param settings Additional settings for the load (may be null)
     * @return A copy of the list
     * @throws ListLoadingException If something went wrong while loading the list
     * @throws NoLoaderException If no loader is registered for the given type
     */
    public static <T, D, K, V> List<T> getList(Class<T> type, D subType, Map<K, V> settings) throws ListLoadingException, NoLoaderException {
        return getList(type, subType, settings, false);
    }

    /**
     * Tries to load the desired list from the cache. If no such entry exists in the cache or <code>forceLoad</code> is set, the corresponding
     * {@link IListLoader} service will be looked up and invoked.
     * 
     * @param <T> The content type of the list
     * @param <D> The discriminator type
     * @param <K> The key type for the parameter map
     * @param <V> The value type for the parameter map
     * @param type The content type of the list
     * @param subType Additional discriminator specifying the "subtype" to load (may be null)
     * @param settings Additional settings for the load (may be null)
     * @param forceLoad True if the load should be forced ignoring cached entries
     * @return A copy of the list
     * @throws ListLoadingException If something went wrong while loading the list
     * @throws NoLoaderException If no loader is registered for the given type
     */
    @SuppressWarnings("unchecked")
    public static <T, D, K, V> List<T> getList(Class<T> type, D subType, Map<K, V> settings, boolean forceLoad) throws ListLoadingException, NoLoaderException {
        final IListLoader<T, D, K, V> s = LIST_LOADERS.getService(type);
        try {
            return STRATEGY.getList(s, type, subType, settings, forceLoad);
        } catch (Exception e) {
            throw new ListLoadingException(type, subType, settings, e);
        }
    }

    /**
     * Loads the object from the given type with the passed key.<br>
     * If such an object is in the cache and <code>forceLoad</code> is false then the cached value will be returned.
     * 
     * @param <T> The object type
     * @param <K> The key type
     * @param type The type to load
     * @param key The key of the object to load
     * @param forceLoad True if a load should be forced
     * @return The object or null if no such object exists
     * @throws NoLoaderException If no loader was registered for the given type
     * @throws ObjectLoadingException If something went wrong during the load
     */
    @SuppressWarnings("unchecked")
    public static <T, K> T load(Class<T> type, K key, boolean forceLoad) throws NoLoaderException, ObjectLoadingException {
        final IObjectHandler s = OBJECT_HANDLERS.getService(type);
        try {
            return STRATEGY.<T, K> load(s, type, key, forceLoad);
        } catch (Exception e) {
            throw new ObjectLoadingException(type, key, e);
        }
    }

    /**
     * Persists the given object.
     * 
     * @param <T> The object type
     * @param value The object to persist
     * @return The persisted object
     * @throws NoLoaderException If no loader was registered for the given type
     * @throws ObjectChangeException If something went wrong while persisting the object
     */
    @SuppressWarnings("unchecked")
    public static <T> T persist(T value) throws NoLoaderException, ObjectChangeException {
        final IObjectHandler s = OBJECT_HANDLERS.getService(value.getClass());
        try {
            return STRATEGY.<T, Object> persist(s, value);
        } catch (Exception e) {
            throw new ObjectChangeException("Error while persisting", value, e);
        }
    }

    /**
     * Updates the persisted object with the given one.
     * 
     * @param <T> The object type
     * @param value The object to update
     * @return The updated object
     * @throws NoLoaderException If no loader was registered for the given type
     * @throws ObjectChangeException If something went wrong while persisting the object
     */
    @SuppressWarnings("unchecked")
    public static <T> T update(T value) throws NoLoaderException, ObjectChangeException {
        final IObjectHandler s = OBJECT_HANDLERS.getService(value.getClass());
        try {
            return STRATEGY.<T, Object> update(s, value);
        } catch (Exception e) {
            throw new ObjectChangeException("Error while updating", value, e);
        }
    }

    /**
     * Removes the persisted object with the given key.
     * 
     * @param <T> The object type
     * @param <K> The key type
     * @param type The type of the object to remove
     * @param key The key of the object to remove
     * @return True if the object was remove, false if no such object existed
     * @throws NoLoaderException If no loader was registered for the given type
     * @throws ObjectChangeException If something went wrong while removing the object
     */
    @SuppressWarnings("unchecked")
    public static <T, K> boolean remove(Class<T> type, K key) throws NoLoaderException, ObjectChangeException {
        final IObjectHandler s = OBJECT_HANDLERS.getService(type);
        try {
            return STRATEGY.<T, K> remove(s, type, key);
        } catch (Exception e) {
            throw new ObjectChangeException("Error while removing", type, key, e);
        }
    }

    /**
     * Registers the given listener on the specified type.
     * 
     * @param l The listener
     * @param type The concrete type
     */
    public static void addDataListener(IDataListener l, Class type) {
        STRATEGY.addDataListener(l, type);
    }

    /**
     * Removes the given listener from the specified type.
     * 
     * @param l The listener
     * @param type The concrete type
     */
    public static void removeDataListener(IDataListener l, Class type) {
        STRATEGY.removeDataListener(l, type);
    }

    /**
     * Registers the given listener on the specified type and all it's subtypes.
     * 
     * @param l The listener
     * @param type The base type
     */
    public static void addStructureDataListener(IDataListener l, Class type) {
        STRATEGY.addStructureDataListener(l, type);
    }

    /**
     * Removes the given listener from the specified type and all it's subtypes.
     * 
     * @param l The listener
     * @param type The base type
     */
    public static void removeStructureDataListener(IDataListener l, Class type) {
        STRATEGY.removeStructureDataListener(l, type);
    }

    /**
     * Fires a changed event for the specified object type description.<br>
     * This event will be forwarded to all concerned {@link IDataListener}s.
     * 
     * @param <T> The object type
     * @param <D> The discriminator type
     * @param <K> The key type for the parameter map
     * @param <V> The value type for the parameter map
     * @param type The type of the concerned object
     * @param subType Additional discriminator specifying the "subtype" which is concerned (may be null)
     * @param settings Additional settings for the load (may be null)
     */
    public static <T, D, K, V> void fireObjectsChangedEvent(Class<T> type, D subType, Map<K, V> settings) {
        STRATEGY.fireObjectsChangedEvent(type, subType, settings);
    }

    /**
     * Fires a changed event for the specified object.<br>
     * This event will be forwarded to all concerned {@link IDataListener}s.
     * 
     * @param <T> The object type
     * @param <K> The key type
     * @param type The type of the object which has changed
     * @param key The key of the object which has changed
     */
    public static <T, K> void fireObjectChangedEvent(Class<T> type, K key) {
        STRATEGY.fireObjectChangedEvent(type, key);
    }
}
